package org.owasp.esapi.util;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import java.security.Key;

import javax.crypto.spec.SecretKeySpec;

import org.owasp.esapi.errors.ConfigurationException;

public class ObjFactoryTest extends TestCase {

    // Purpose of this is to prevent a default, no-arg, public CTOR to be generated.
    // We want to prevent this so we can use this class to test the case of where
    // ObjectFactory<T>.make() throws an IllegalAccessException.
    @SuppressWarnings("unused")
    private ObjFactoryTest(int i) { ; }

    /**
     * Instantiates a new object factory test.
     *
     * @param testName
     *            the test name
     */
    public ObjFactoryTest(String testName) {
        super(testName);
    }

    /**
     * {@inheritDoc}
     * @throws Exception
     */
    protected void setUp() throws Exception {
        // none
    }

    /**
     * {@inheritDoc}
     * @throws Exception
     */
    protected void tearDown() throws Exception {
        // none
    }

    /**
     * Run all the test cases in this suite.
     * This is to allow running from {@code org.owasp.esapi.AllTests}.
     *
     * @return the test
     */
    public static Test suite() {
        TestSuite suite = new TestSuite(ObjFactoryTest.class);
        return suite;
    }

    /** Test that NullCipher object is correctly returned. */
    public void testMakeNullCipher() throws ConfigurationException {
        String className = "javax.crypto.NullCipher";
        javax.crypto.Cipher nullCipher =
                ObjFactory.make(className, "NullCipher");
        assertTrue( nullCipher instanceof javax.crypto.NullCipher );
        System.out.println("W00t! Watch out NSA...we have a NullCipher and we're not afraid to use it!");
    }

    /** Test that InstantiationException is thrown as the root cause when the
     * specified class name is an abstract class or interface.
     */
    public void testInterface() throws ConfigurationException {
        Key key = null;
        try {
            key = ObjFactory.make("java.security.Key", "Key");
            assertFalse("Should not be reached - interface or abstract class", key != null);
        } catch(ConfigurationException ex) {
            Throwable cause = ex.getCause();
            assertTrue( cause instanceof InstantiationException);
        }
    }

    /** Test that IllegalAccessException is thrown as the root cause when the
     *  specified class has no public, no-arg CTOR. Cipher has only a protected
     *  CTOR that takes multiple parameters.
     *
     *  FIXME: Need new test. This also throws an InstantiationException as the
     *  root cause. The goal is to have it throw IllegalAccessException.
     */
    public void testMakeNoPublicConstructor() throws ConfigurationException {
        ObjFactoryTest oft = null;
        try {
            // CHECKME: As I read
            //      http://java.sun.com/docs/books/tutorial/reflect/member/ctorTrouble.html
            // this should cause an IllegalAccessException to be thrown because it has no public,
            // no-arg CTOR. However, it doesn't. It throws a InstantiationException instead.
            oft = ObjFactory.make(ObjFactoryTest.class.getName(), "ObjectFactoryTest");
            assertFalse("Should not be reached - no public CTOR", oft != null);
        } catch(ConfigurationException ex) {
            Throwable cause = ex.getCause();
            // assertTrue( cause instanceof IllegalAccessException);
            assertTrue( cause instanceof InstantiationException);
        }
    }

    /** Test that ClassNotFoundException is thrown as the root cause when
     * the class name to be created is not a class name that exists anywhere.
     */
    public void testMakeNoSuchClass() throws ConfigurationException {
        Object obj = null;

        try {
            obj = ObjFactory.make("kevin.wall.HasNoClass", "Object");
            assertFalse("Should not be reached - no such class", obj != null);
        } catch(ConfigurationException ex) {
            Throwable cause = ex.getCause();
            assertTrue( cause instanceof ClassNotFoundException);
        }
    }

    /** Test that ClassCastException is thrown as the root cause when the
     * created class is not a subclass / does not implement the specified type.
     * (In this case, String is not a subclass / does not implement Key.)
     */
    public void testMakeNotASubclass() throws ConfigurationException {
        Key key = null;
        try {
            key = ObjFactory.make("java.lang.String", "testMakeNotASubclass");
            assertFalse("Should not be reached - not a subclass", key != null);
        } catch(ConfigurationException ex) {
            Throwable cause = ex.getCause();
            System.out.println("DEBUG: Cause was: " + cause.getClass().getName());
            assertTrue( cause instanceof ClassCastException);
        } catch(ClassCastException ccex) {
            assertTrue("Caught expected class cast exception", true);
        }
    }

    /** Test that IllegalArgumentException is thrown as the cause when the
     * class name is specified as an empty string.
     */
    public void testMakeEmptyClassName() throws ConfigurationException {
        Object obj = null;
        try {
            obj = ObjFactory.make("", "testMakeEmptyClassName");
            assertFalse("Should not be reached - not a subclass", obj != null);
        } catch(ConfigurationException ex) {
            Throwable cause = ex.getCause();
            assertTrue( cause instanceof IllegalArgumentException);
        }
    }

    /** Test that some other exception is thrown from the no-arg, public CTOR as the
     * root cause. Had to use special external class here because strangely, this didn't
     * work as an inner class. (Threw InstantiationException in that case instead.)
     */
    public void testMakeOtherException() throws ConfigurationException {
        @SuppressWarnings("unused")
        ThePrefectClass ford = null;
        try {
            ford = ObjFactory.make("org.owasp.esapi.util.ThePrefectClass", "ThePrefectClass");
        } catch(ConfigurationException ex) {
            Throwable cause = ex.getCause();
            assertTrue( cause instanceof UnsupportedOperationException);
        }
    }

    /** Test case where typeName is null or empty string. */
    public void testNullorEmptyTypeName() throws ConfigurationException {
        String className = "javax.crypto.NullCipher";
        javax.crypto.Cipher nullCipher =
                ObjFactory.make(className, null);
        assertTrue( nullCipher instanceof javax.crypto.NullCipher );
        nullCipher =
            ObjFactory.make(className, "");
        assertTrue( nullCipher instanceof javax.crypto.NullCipher );
    }

    /** Test case where no-arg CTOR does not exist. By all indications from
     * Javadoc for {@code Class.newInstance()} one would think this should
     * throw an {@code IllegalAccessException} because {@code SecretKeySpec}
     * has two public CTORs that both take arguments. */
    public void testMakeCipher() throws ConfigurationException {
        try {
            String className = "javax.crypto.spec.SecretKeySpec";
            javax.crypto.spec.SecretKeySpec skeySpec =
                (SecretKeySpec) ObjFactory.make(className, "SecretKeySpec");
            // Should not get to here. Exception is expected.
        } catch(ConfigurationException ex) {
            Throwable cause = ex.getCause();
            assertTrue( cause instanceof InstantiationException);
        }
    }

    /** Test cache. Create 100k JavaEncryptor instances with cache enabled (the
     * default), and then create 100k instances with cache disabled. Time each.
     * The cached version should save some time.
     */
    public void testObjFactoryCache() throws Exception {
        final int reps = 100000;
        System.out.println("testObjFactoryCache: " + reps + " iterations.");
        org.owasp.esapi.reference.crypto.JavaEncryptor je = null;
        String clz = "org.owasp.esapi.reference.crypto.JavaEncryptor";

        long startCacheEnabled = System.nanoTime();
        for ( int i = 0; i < reps; i++ ) {
            je = (org.owasp.esapi.reference.crypto.JavaEncryptor) ObjFactory.make(clz, "JavaEncryptor");
            assertNotNull( je );
        }
        long stopCacheEnabled = System.nanoTime();

        ObjFactory.setCache( false );   // Disable cache

        long startCacheDisabled = System.nanoTime();
        for ( int i = 0; i < reps; i++ ) {
            je = (org.owasp.esapi.reference.crypto.JavaEncryptor) ObjFactory.make(clz, "JavaEncryptor");
            assertNotNull( je );
        }
/*
 * TODO - Replace all this with the JMH benchmark harness stuff as similar to
 * what is in ObjFactoryBenchmark. See GitHub issue #498 for further details.
 * Unfortunately, we need to do this until then because JIT manipulations are
 * sometimes making the cache disabled time take less than with caching enabled.
 * In fact, when we start using JMH for this, we probably ought to move this
 * entire test to ObjFactoryBenchmark.
 *
        long stopCacheDisabled = System.nanoTime();

        long durationEnabled  = stopCacheEnabled - startCacheEnabled;
        long durationDisabled = stopCacheDisabled - startCacheDisabled;
        System.out.println("testObjFactoryCache: Time with cache ENABLED (nanosec):  " + durationEnabled );
        System.out.println("testObjFactoryCache: Time with cache DISABLED (nanosec): " + durationDisabled );

        assertTrue( durationEnabled < durationDisabled );
 */
    }
}
